package com.study.springsecurity.security.config;

import com.alibaba.fastjson.JSON;
import com.study.springsecurity.constant.CommonConstant.AuthType;
import com.study.springsecurity.constant.CommonConstant.Role;
import com.study.springsecurity.pojo.vo.ApiRoleCodeVo;
import com.study.springsecurity.security.voters.PermitAllVoter;
import com.study.springsecurity.service.ApiInfoService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.access.vote.AuthenticatedVoter;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.util.AntPathMatcher;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 对ExpressionBasedFilterInvocationSecurityMetadataSource的扩展
 * 从数据库获取当前访问的地址需要哪些权限规则
 */
@Slf4j
public class ApiFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {

    private FilterInvocationSecurityMetadataSource superFilterInvocationSecurityMetadataSource;

    private ApiInfoService apiInfoService;

    public ApiFilterInvocationSecurityMetadataSource(FilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource, ApiInfoService apiInfoService) {
        this.superFilterInvocationSecurityMetadataSource = filterInvocationSecurityMetadataSource;
        this.apiInfoService = apiInfoService;
    }

    /*** ant 路径匹配规则 */
    private final static AntPathMatcher ANT_PATH_MATCHER = new AntPathMatcher();

    /**
     * 这个是主要的方法，该方法会需要返回url需要的权限列表
     */
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        FilterInvocation filterInvocation = (FilterInvocation) object;
        String requestUrl = filterInvocation.getRequestUrl();

        Map<String, List<ApiRoleCodeVo>> apiMap = apiInfoService.queryAllApiAndNeedRole(); // TODO 该查询后续可以加缓存，避免每次查库
        List<ApiRoleCodeVo> permitAllApi = MapUtils.getObject(apiMap, AuthType.PERMIT_ALL,new ArrayList<>());
        for (ApiRoleCodeVo apiRoleCodeVo : permitAllApi) {
            if (ANT_PATH_MATCHER.match(apiRoleCodeVo.getUrlPath(), requestUrl)) {
                log.info("请求地址为【{}】和忽略规则【{}】匹配", requestUrl, JSON.toJSONString(apiRoleCodeVo));
                return PermitAllVoter.PermitAllConfigAttribute.createSingletonList();
            }
        }

        List<ApiRoleCodeVo> anonymousApi = MapUtils.getObject(apiMap, AuthType.ANONYMOUS,new ArrayList<>());
        for (ApiRoleCodeVo apiRoleCodeVo : anonymousApi) {
            if (ANT_PATH_MATCHER.match(apiRoleCodeVo.getUrlPath(), requestUrl)) {
                log.info("请求地址为【{}】和匿名规则【{}】匹配", requestUrl, JSON.toJSONString(apiRoleCodeVo));
                return SecurityConfig.createList(AuthenticatedVoter.IS_AUTHENTICATED_ANONYMOUSLY);
            }
        }
        List<ApiRoleCodeVo> authenticatedApi = MapUtils.getObject(apiMap, AuthType.AUTHENTICATED,new ArrayList<>());
        for (ApiRoleCodeVo apiRoleCodeVo : authenticatedApi) {
            if (ANT_PATH_MATCHER.match(apiRoleCodeVo.getUrlPath(), requestUrl)) {
                log.info("请求地址为【{}】和登录即可访问规则【{}】匹配", requestUrl, JSON.toJSONString(apiRoleCodeVo));
                return SecurityConfig.createList(AuthenticatedVoter.IS_AUTHENTICATED_FULLY);
            }
        }

        List<ApiRoleCodeVo> hasAnyRoleApi = MapUtils.getObject(apiMap, AuthType.HAS_ANY_ROLE,new ArrayList<>());
        for (ApiRoleCodeVo apiRoleCodeVo : hasAnyRoleApi) {
            if (ANT_PATH_MATCHER.match(apiRoleCodeVo.getUrlPath(), requestUrl) && CollectionUtils.isNotEmpty(apiRoleCodeVo.getRoleCodeList())) {
                log.info("请求地址为【{}】和角色规则【{}】匹配", requestUrl, JSON.toJSONString(apiRoleCodeVo));
                List<String> roleCodeList = apiRoleCodeVo.getRoleCodeList();
                String[] roleArr = roleCodeList.stream().map(s -> Role.ROLE_PREFIX + s).collect(Collectors.toSet()).toArray(new String[0]);
                return SecurityConfig.createList(roleArr);
            }
        }

        // 若查询数据库没有匹配上，则调用ExpressionBasedFilterInvocationSecurityMetadataSource处理，也就是说以数据库配置优先，可覆盖代码中的配置
        return superFilterInvocationSecurityMetadataSource.getAttributes(object);
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return superFilterInvocationSecurityMetadataSource.getAllConfigAttributes();
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return superFilterInvocationSecurityMetadataSource.supports(clazz);
    }
}

